Skip to content

feat: add phone_calls WatermelonDB table#348

Open
Charlie McCowan (CharlieMc0) wants to merge 2 commits into
mainfrom
feat/phone-calls-table
Open

feat: add phone_calls WatermelonDB table#348
Charlie McCowan (CharlieMc0) wants to merge 2 commits into
mainfrom
feat/phone-calls-table

Conversation

@CharlieMc0

@CharlieMc0 Charlie McCowan (CharlieMc0) commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add phone_calls WatermelonDB table (schema v19 → v20) to persist Bland AI phone call data locally
  • New src/lib/db/phone_call/ module with model, CRUD operations, and types following existing patterns (project/media)
  • Indexed fields: call_id, conversation_id, offer_message_id for efficient lookups
  • Exports from @anuma/sdk/react: PhoneCall, StoredPhoneCall, PhoneCallOperationsContext, createPhoneCallOp, getPhoneCallByOfferOp, getPhoneCallsByConversationOp, updatePhoneCallOp

Integration

Import phone call operations from @anuma/sdk/react:

import {
  createPhoneCallOp,
  getPhoneCallByOfferOp,
  updatePhoneCallOp,
  type PhoneCallOperationsContext,
} from '@anuma/sdk/react';

const ctx: PhoneCallOperationsContext = {
  database,
  phoneCallsCollection: database.get('phone_calls'),
};

Breaking Changes

None — additive schema migration only.


Note

Medium Risk
Introduces a schema version bump and new migration; while additive, any issues in table creation/indexing or model registration could break local DB initialization or upgrades.

Overview
Adds a new WatermelonDB-backed phone_calls storage module to persist Bland AI call lifecycle data (request/response, status, timestamps) and query it by offer message/conversation.

Bumps SDK_SCHEMA_VERSION from 19→20 with a migration that creates the indexed phone_calls table, registers the PhoneCall model, and re-exports the new model/types/operations from @anuma/sdk/react.

Written by Cursor Bugbot for commit 79a4114. This will update automatically on new commits. Configure here.

Add new phone_calls table (schema v19 → v20) to persist Bland AI phone
call data locally. Includes model, CRUD operations, types, and barrel
exports following the existing project/media table patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Dependency graph

Changed files are highlighted in green.

flowchart LR

subgraph 0["src"]
subgraph 1["expo"]
2["index.ts"]
end
subgraph 3["lib"]
subgraph 4["db"]
5["manager.ts"]
6["schema.ts"]
subgraph 7["phone_call"]
8["models.ts"]
9["index.ts"]
A["operations.ts"]
B["types.ts"]
end
end
end
subgraph C["react"]
D["index.ts"]
E["useDatabaseManager.ts"]
end
subgraph F["server"]
G["index.ts"]
H["storage.ts"]
end
end
2-->5
2-->5
2-->6
5-->6
6-->8
9-->8
9-->A
9-->B
A-->8
A-->B
D-->5
D-->5
D-->9
D-->6
D-->E
E-->5
G-->5
G-->5
G-->6
G-->H
H-->5

style 6 fill:lime,color:black
style 8 fill:lime,color:black
style 9 fill:lime,color:black
style A fill:lime,color:black
style B fill:lime,color:black
style D fill:lime,color:black
Loading

@github-actions

github-actions Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 58.43% 2068 / 3539
🔵 Statements 56.73% 2140 / 3772
🔵 Functions 62.47% 373 / 597
🔵 Branches 45.18% 1196 / 2647
File CoverageNo changed files found.
Generated in workflow #298 for commit 79a4114 by the Vitest Coverage Report Action

@github-actions github-actions Bot added the api-changes PR changes the public API surface label Mar 10, 2026
@github-actions

github-actions Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

API Changes

@anuma/sdk/expo

✏️ Modified

SDK_SCHEMA_VERSION (variable)
- SDK_SCHEMA_VERSION = 19
+ SDK_SCHEMA_VERSION = 20

@anuma/sdk/react

🆕 Added
createPhoneCallOp (function) · CreatePhoneCallOptions (interface) · deletePhoneCallsByConversationOp (function) · getPhoneCallByOfferOp (function) · getPhoneCallsByConversationOp (function) · PhoneCall (class) · PhoneCallOperationsContext (interface) · PhoneCallStatus (type) · phoneCallToStored (function) · StoredPhoneCall (interface) · updatePhoneCallOp (function) · UpdatePhoneCallOptions (interface)

✏️ Modified

SDK_SCHEMA_VERSION (variable)
- SDK_SCHEMA_VERSION = 19
+ SDK_SCHEMA_VERSION = 20

@github-actions

github-actions Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Integration Check ✅

main PR
starter-next build
starter-next e2e
starter-mini build
starter-telegram build
dashboard build
text build
cli build

@CharlieMc0 Charlie McCowan (CharlieMc0) marked this pull request as ready for review March 10, 2026 20:41
@rutwik2001

Copy link
Copy Markdown
Contributor
1. created_at / updated_at not set on create — operations.ts:43-48

  The createPhoneCallOp never sets created_at or updated_at via _setRaw. WatermelonDB does not auto-populate these — you need to set them
  explicitly. The @date decorator on the model reads from raw, but nothing writes the initial value. Records will have null/0 timestamps.

  record._setRaw("created_at", Date.now());
  record._setRaw("updated_at", Date.now());

2. updated_at not bumped on update — operations.ts:95-98

  updatePhoneCallOp updates status/response but never sets updated_at. The field will remain stale forever after creation.

3. Unsafe cast record.status as PhoneCallStatus — operations.ts:19

  If the DB ever contains a corrupted/unexpected value, this silently passes through as a valid type.
  

4. request/response schema mismatch — Schema declares isOptional: true but the model uses @text (not @text with isOptional). In
  createPhoneCallOp you default to "" which works around it, but the model type says string (never null) while the schema says optional.
  Inconsistent but not broken since you always write "".

5. No deletePhoneCallOp — Every other entity module has delete. Intentional? If calls are never cleaned up, the table grows unbounded.

6. getPhoneCallByOfferOp returns first match silently — operations.ts:63. If there are somehow duplicates (no unique constraint
  enforced), this silently picks one. Consider logging a warning if results.length > 1.

Hard-deletes all phone call records for a conversation. Prevents
unbounded table growth when conversations are cleaned up.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@CharlieMc0

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review Pasumarthi Rutwik (@rutwik2001)! Here's the breakdown:

1. created_at/updated_at not set on create — WatermelonDB auto-populates these. The Project model follows the same pattern (never sets them in _setRaw) and works correctly.

2. updated_at not bumped on update — WatermelonDB's record.update() automatically bumps updated_at.

3. Unsafe cast record.status as PhoneCallStatus — Matches the existing pattern across all SDK models. Not worth special-casing one table.

4. Schema isOptional vs @text mismatch — Minor inconsistency but not broken since we always write "". Low priority cleanup.

5. No delete operation — Good catch. Added deletePhoneCallsByConversationOp in 79a4114 — hard-deletes all phone call records for a conversation to prevent unbounded growth.

6. getPhoneCallByOfferOp silently picks firstofferMessageId is a WatermelonDB unique message ID so duplicates can't occur in practice.

Comment thread src/lib/db/schema.ts
*/
export const SDK_SCHEMA_VERSION = 19;
export const SDK_SCHEMA_VERSION = 20;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch is ~20 commits behind main, and the schema version has moved to v27. This migration targets v19→20, but v20 on main is already the updated_at index migration for memory_vault. We need a rebase so the phone_calls migration lands as v28 and doesn't collide with existing versions.

@kingpinXD

Copy link
Copy Markdown
Contributor

Schema version collision (blocking) — The branch HEAD is still 79a41147 from April and hasn't been rebased. Main is now at SDK_SCHEMA_VERSION = 27 and v20 is already taken by the memory_vault.updated_at index migration. Six more migrations have landed since (v21 embedding, v22 is_system, v23 conversation_summaries, v24 folder context, v25 saved_tools, v26 app_files, v27 tool_call_events). As-is, merging this would either silently overwrite the v20 migration step or fail outright. We need to rebase, bump to v28, update the version comment block in schema.ts, and add the phone_calls migration after the v27 step.

@kingpinXD

Copy link
Copy Markdown
Contributor

src/lib/db/schema.tsgetPhoneCallsByConversationOp sorts by created_at after filtering by conversation_id, so without an index WatermelonDB will full-scan all rows matching the conversation and sort in memory. Sibling tables that sort by created_at all index it (media, app_files, saved_tools, vault_folders). Lets add isIndexed: true on created_at in both the table schema and the migration.

@kingpinXD

Copy link
Copy Markdown
Contributor

src/lib/db/phone_call/models.ts — Every other table with a conversation_id FK declares a belongs_to association (see media/models.ts, appFiles/models.ts, and the inverse has_many on Conversation). Without it, code that wants to relate phone calls back to a conversation has to query manually and WatermelonDB can't validate the FK direction. Lets add the association and consider the inverse has_many on Conversation if call-from-conversation traversal is expected.

@kingpinXD

Copy link
Copy Markdown
Contributor

src/lib/db/phone_call/operations.ts:38-47createPhoneCallOp doesn't write created_at / updated_at explicitly and relies on WatermelonDB's decorator auto-population. Sibling ops (savedTools/operations.ts, appFiles/operations.ts) set both timestamps explicitly inside the create callback so behavior isn't decorator-dependent. Lets match that pattern, and bump updated_at explicitly inside updatePhoneCallOp too.

@kingpinXD

Copy link
Copy Markdown
Contributor

src/lib/db/phone_call/models.ts:13 — Sibling models (savedTools/models.ts:22, appFiles/models.ts:13) use @readonly @date('created_at') to prevent accidental rewrites of the creation timestamp. The plain @date here lets callers mutate createdAt via _setRaw which is almost certainly not the intent. Lets add @readonly.

@kingpinXD

Copy link
Copy Markdown
Contributor

src/lib/db/phone_call/models.ts:10status is typed as plain string on the model and we cast to PhoneCallStatus externally in operations.ts:18. The cast hides the fact that status could be any string at runtime — if a future migration ever writes a new status value, downstream consumers silently get an unknown variant typed as PhoneCallStatus. Lets type the model field with the union directly so the cast is unnecessary and write-time typos get caught.

/**
* Delete all phone call records for a conversation.
*/
export async function deletePhoneCallsByConversationOp(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets swap the per-record destroyPermanently() loop for query(...).destroyAllPermanently() — one batched delete inside the write block, no need to materialize the full result set first.

/**
* Stored representation of a phone call in the database.
*/
export interface StoredPhoneCall {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniqueId field reads a bit ambiguous next to callId. Could we either rename it to id to match StoredMedia / StoredAppFile, or sharpen the comment so its clear this is the Watermelon row id and not the Bland call id?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-changes PR changes the public API surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants